#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/applications-module.h"
#include "ns3/mobility-module.h"
#include "ns3/tools-module.h"
#include "ns3/wifi-module.h"
#include "ns3/internet-stack-helper.h"
#include "ns3/ipv4-address-helper.h"
#include "ns3/ac_file.h"
#include "ns3/integer.h"
#include "ns3/netanim-module.h"
#include <math.h>
#include <fstream>
#include <iostream>



using namespace ns3;

bool nodeWithFileWasInAoI=false;
bool nodeWithFileLeftAreaOfInterest=false;
uint32_t firstTimeFileinAc=0;
uint32_t lastTimeNodeInAc=0;
IntegerValue kkk;
EventId N_InAoi;
double Simduration=0;

static void CourseChange (std::string context, Ptr<const MobilityModel> mobility) {
	Vector pos = mobility->GetPosition ();
 	Vector vel = mobility->GetVelocity ();
    }

void printFinishedTime(uint32_t numberOfNodes){
    std::string fileNameOutput="SimOutput/FinishedTime.txt";
    std::ofstream myfile(fileNameOutput.c_str(),std::ios::app);
    if(!myfile.is_open())
        myfile.open (fileNameOutput.c_str());
    
    myfile<<"N_NODES "<<numberOfNodes<<" FT "<<firstTimeFileinAc<<" LT "<<lastTimeNodeInAc<<std::endl;
    myfile.close();
    
}

void printFilesOwnedByNodes(ApplicationContainer app)
{
    for (uint32_t i=0; i<app.GetN(); i++) {
    
        DynamicCast<AirCacheApplication> (app.Get(i))->printListofFileNames();
    
    }
}


static void NoOneInAoI(uint32_t aoi_x, uint32_t aoi_y, uint32_t dist, ApplicationContainer  allApps, NodeContainer c){
    

     NS_ASSERT (N_InAoi.IsExpired ());


    bool NoOneInAreaOfInterest=true;
   
       
    double distance=std::sqrt((c.Get(0)->GetObject<MobilityModel>()->GetPosition().x-aoi_x) * (c.Get(0)->GetObject<MobilityModel>()->GetPosition().x-aoi_x)  + (c.Get(0)->GetObject<MobilityModel>()->GetPosition().y-aoi_y)  * (c.Get(0)->GetObject<MobilityModel>()->GetPosition().y-aoi_y) );


    if(distance<=dist)
    {
        nodeWithFileWasInAoI=true;
        NoOneInAreaOfInterest=false;
        if(firstTimeFileinAc==0)
        firstTimeFileinAc=Simulator::Now().GetSeconds();
    }
    else{
        if(nodeWithFileWasInAoI)
        {
            nodeWithFileLeftAreaOfInterest=true;
            NoOneInAreaOfInterest=true;
        }
    }
    
    
    double X;
    double Y;
    double distanza;
    if(nodeWithFileLeftAreaOfInterest){
    for (uint32_t i=0; i<c.GetN(); i++) {

        Ptr<MobilityModel> mob=c.Get(i)->GetObject<MobilityModel>();
        Vector pos=mob->GetPosition();
        X=pos.x-aoi_x;
        Y=pos.y-aoi_y;
        distanza=std::sqrt((X*X)+(Y*Y));
    
    /* std::cout<<"_____________app___"<< DynamicCast<AirCacheApplication> (allApps.Get(i))->fileList.size()<<"_____node________ "<<DynamicCast<AirCacheApplication> (allApps.Get(i))->node_id<<"_____dist____"<<distanza<<std::endl;
    */

        
        if(distanza <=dist+50 && DynamicCast<AirCacheApplication> (allApps.Get(i))->fileList.size()>0)
        {NoOneInAreaOfInterest=false;}
        
    }
    }


       
    
    
  
    
     N_InAoi=Simulator::Schedule (MilliSeconds(1000), &NoOneInAoI, aoi_x, aoi_y, dist,allApps, c);
     if(nodeWithFileLeftAreaOfInterest && NoOneInAreaOfInterest)
     {

         Simulator::Cancel(N_InAoi);
         std::cout<<"£££££££££££££££££££££££££££££££££££££ "<<Simulator::Now().GetSeconds()<<std::endl;
         lastTimeNodeInAc=Simulator::Now().GetSeconds();
         printFinishedTime(c.GetN());
         printFilesOwnedByNodes(allApps);
         Simulator::Stop(Seconds(Simulator::Now().GetSeconds()));
         
     }
    
    if(Simulator::Now().GetSeconds()>Simduration-1)
    {
        if(nodeWithFileLeftAreaOfInterest)
        lastTimeNodeInAc=Simulator::Now().GetSeconds();
        else
            lastTimeNodeInAc=0;
        
        printFinishedTime(c.GetN());
        
    }
    
}




NS_LOG_COMPONENT_DEFINE ("Main");

int main (int argc, char *argv[])
{
    LogComponentEnable("AirCacheApp",LOG_INFO);
    LogComponentEnable("AirCacheApp",LOG_DEBUG);

    
    std::string traceFile;
    std::string paramFile;
    std::ifstream mobileInput;
    
    
	int nodeNum = 0;
	double duration = 0;

    CommandLine cmd;
	cmd.AddValue("traceFile","NS3 movement trace file",traceFile);
    cmd.AddValue("paramFile","NS3 parameters file",paramFile);
    
	cmd.Parse(argc,argv);
    
    if(traceFile.empty()) {
		std::cout << "Usage of " << argv[0] << ":\n" << "./waf --run \"script --traceFile=/path/to/tracefile\"\n";
	}
    
    mobileInput.open(paramFile.c_str(),std::ios::in);
    std::string str;
    if (mobileInput.is_open()) {
        // print file:
         
        
        while (mobileInput.good()) {
            mobileInput>>str;
            if(str=="val(nn)")
                mobileInput>>nodeNum;
            if(str=="val(duration)")
                mobileInput>>duration;
        }
        mobileInput.close();
    }
    else {
        // show message:
        std::cout << "Error opening file";
    }
    Simduration=duration;
   // std::cout<<"NODE NUMBERS : "<<nodeNum<<"valDuration : "<<duration<<std::endl;
    
    Ns2MobilityHelper ns3mobility = Ns2MobilityHelper(traceFile);

    
    
    Config::SetDefault ("ns3::WifiRemoteStationManager::FragmentationThreshold", StringValue ("2200"));
    
    // turn off RTS/CTS for frames below 2200 bytes
    Config::SetDefault ("ns3::WifiRemoteStationManager::RtsCtsThreshold", StringValue ("2200"));
    
    //TODO(check meaning) Fix non-unicast data rate to be the same as that of unicast : not to confuse it with beaconing ?
    std::string phyMode ("OfdmRate54Mbps");//802.11ag
    Config::SetDefault ("ns3::WifiRemoteStationManager::NonUnicastMode", StringValue (phyMode));
    
    NodeContainer c;
    c.Create (nodeNum);
    
    
    ns3mobility.Install();
	Config::Connect("/NodeList/*/$ns3::MobilityModel/CourseChange",MakeCallback(&CourseChange));
    
    YansWifiPhyHelper wifiPhy = YansWifiPhyHelper::Default ();
    YansWifiChannelHelper wifiChannel;
    wifiChannel.SetPropagationDelay ("ns3::ConstantSpeedPropagationDelayModel");
    
    wifiChannel.AddPropagationLoss ("ns3::RangePropagationLossModel",
                                    "MaxRange", DoubleValue (100.0));
    wifiPhy.SetChannel (wifiChannel.Create ());
    WifiHelper wifi = WifiHelper::Default ();
    wifi.SetStandard (WIFI_PHY_STANDARD_80211g);//just used for computing DCF related timers
    wifi.SetRemoteStationManager ("ns3::MinstrelWifiManager"); //--near to optimal performance and the only one with native support in linux kernel
    
    NqosWifiMacHelper wifiMac = NqosWifiMacHelper::Default ();
    wifiMac.SetType("ns3::AdhocWifiMac");
    
    
    
    NetDeviceContainer devices;
    devices= wifi.Install (wifiPhy, wifiMac, c);
    
  
           
    InternetStackHelper internet;
    internet.Install (c);
    Ipv4AddressHelper ipv4;
    ipv4.SetBase ("10.0.0.0", "255.0.0.0");
    ipv4.Assign (devices);
    
    
    
    BasicEnergySourceHelper   basicSourceHelper;
    EnergySourceContainer sources;
    // configure energy source
    for (uint32_t i=0; i<(uint32_t)nodeNum; i++) {
        basicSourceHelper.Set ("BasicEnergySourceInitialEnergyJ", DoubleValue (20000-(i*100)));
        sources.Add(basicSourceHelper.Install (c.Get(i)));
        
    }
    // install source
    /* device energy model */
    WifiRadioEnergyModelHelper radioEnergyHelper;
    // configure radio energy model
    radioEnergyHelper.Set ("TxCurrentA", DoubleValue (0.0174));
    // install device model
    DeviceEnergyModelContainer deviceModels = radioEnergyHelper.Install (devices, sources);
    
    uint32_t aoi_x=0;
    uint32_t aoi_y=0;
    uint32_t dist=0;
    //extract data regarding the area of interest
    std::ifstream aoiInput;
    aoiInput.open("AoICoord.txt",std::ios::in);
    std::string str1;
    if (aoiInput.is_open()) {
        
        while (aoiInput.good()) {
            aoiInput>>str1;
            if(str1=="aoi")
            {
                aoiInput>>aoi_x;
                aoiInput>>aoi_y;
                
            }
            if(str1=="dist")
            {
                aoiInput>>dist;
                
            }
        }
        aoiInput.close();
    }
    else {
        // show message:
        NS_LOG_INFO("Error opening file");
    }

    
    
    
    AirCacheHelper ac0 ("ns3::UdpSocketFactory");
    ac0.SetConstantRate (DataRate (54000000));
    ac0.SetAttribute ("FI_LENGTH", UintegerValue (32));
    ac0.SetAttribute ("NodeID", IntegerValue (c.Get(0)->GetId()));
    
    ac0.SetAttribute("X_COORD",UintegerValue(aoi_x));
    ac0.SetAttribute("Y_COORD",UintegerValue(aoi_y));
    ac0.SetAttribute("DIST",UintegerValue(dist));


    
    ac0.SetAttribute ("Energy",PointerValue(sources.Get (0)));
    
    IntegerValue node_id=c.Get(0)->GetId();
    ac0.SetAttribute ("FileName", StringValue ("File_Nodo_"+node_id.SerializeToString(MakeUintegerChecker<uint32_t> ())));
    ac0.SetAttribute ("FileSize", UintegerValue (250000));
    ac0.SetAttribute ("AoIFile", StringValue ("AoICoord.txt"));
    
    
    ApplicationContainer Allapp;
    ApplicationContainer apps0 = ac0.Install (c.Get(0));
    apps0.Start (MilliSeconds(1000));
    apps0.Stop (Seconds (duration));
    Allapp.Add(apps0);
    
    UniformVariable battery (10,100);
    UniformVariable fileSize (1,1000000);
    UniformVariable startTime (1,1000);
    UniformVariable k (1,5);
    
    IntegerValue n_id;
    UintegerValue fs;
    
    for (uint32_t i=1; i<(uint32_t)nodeNum; i++) {
        n_id=c.Get(i)->GetId();
        AirCacheHelper ac ("ns3::UdpSocketFactory");
        ac.SetConstantRate (DataRate (54000000));
        ac.SetAttribute ("FI_LENGTH", UintegerValue (32));
       
        ac.SetAttribute("X_COORD",UintegerValue(aoi_x));
        ac.SetAttribute("Y_COORD",UintegerValue(aoi_y));
        ac.SetAttribute("DIST",UintegerValue(dist));
        
        ac.SetAttribute ("Energy",PointerValue(sources.Get (i)));
        
        ac.SetAttribute ("AoIFile", StringValue ("AoICoord.txt"));
        ac.SetAttribute ("NodeID", n_id);
        ac.SetAttribute ("FileName", StringValue ("Vuoto"));
        fs=fileSize.GetValue();
        ac.SetAttribute ("FileSize", UintegerValue (fs));
        ApplicationContainer apps = ac.Install (c.Get(i));
        apps.Start (MilliSeconds(startTime.GetValue()));
        apps.Stop (Seconds (duration));
        Allapp.Add(apps);
        
        
    }
    
   N_InAoi=Simulator::Schedule (Seconds(1), &NoOneInAoI, aoi_x, aoi_y, dist ,Allapp, c);

    AnimationInterface::SetNodeColor(c.Get(0), 0, 255, 0);

    AnimationInterface anim ("AirCacheSimulationMovement.xml"); // Mandatory
    //anim.EnablePacketMetadata (true);
    
    //neded when the energy model is present
    Simulator::Stop(Seconds(duration+1));
    
   
    Simulator::Run();
   
    Simulator::Destroy ();
    
    
    
    return 0;
}
//

